Effective-Objective-C读书笔记(7)

系统框架

第47条:熟悉系统框架

Fundation、CoreFundation、CFNetwork(封装 BSD socket,抽象成易于使用的网络接口)、CoreAudio、AVFoundation、CoreData、CoreText 等。

第48条:多用 block 枚举,少用 for 循环

遍历 collection 可以使用 for 循环NSEnumerator快速遍历法block 枚举本身通过 GCD 来并发这行遍历操作,无需另外编写代码。而其它遍历方式无法轻易实现这一点。如果已知待遍历的 collection 含有何种对象,则应该修改签名,指出对象的具体类型。

第49条:对自定义内存管理语句 collection 使用无缝桥接

使用__bridge进行桥接 Foundation 与 CoreFoundation 框架中的 C 怨言数据结构之间转换:

1
2
NSArray *anArray = @[@1, @2, @3];
CFArrayRef aCFArray = (__bridge CFArrayRef)(anArray);

转换涉及到的内存管理:

  • __bridge:告诉ARC 如何处理 Objective-C 对象,ARC 仍然具备这个对象的所有权。
  • __bridge_retained:ARC 将交出对象的所有权。
  • __bridge_transfer:反向转换,比如将 CFArrayRef 转换为 NSArray *。并令 ARC 获得对象所有权。
  • 使用 CoreFoundation 框架最后一定要调用与之对应的CFRelease()

第50条:构建缓存时选用 NSCache 而非 NSDictionary

对比:

  • 当系统资源耗尽,可以自动删减缓存。使用NSDictionary需要自己 hook,在内存低警告的时候手动删减缓存。NSCache 是线程安全的,NSDictionary 不具备这种优势。
  • 可以给 NSCache 对象设置上限,用于限制缓存中的对象总个数。
  • NSPurgeableData 对象与 NSCache 搭配使用,可以实现自动清除数据的功能。也就是说,当 NSPurgeableData 对象锁斩内存被系统所丢弃,该对象自身也可会从缓存中移除。

第51条:精简 initialize 与 load 的实现代码

对于加入runtime系统中的每个类和分类,都会调用这个方法,并且只调用一次。如果分类和类里面都定义了load方法,会先调用类里的,在调用分类里的。load方法再调用前,会加载父类的load方法。执行该load方法时,系统还处于“脆弱状态”(fragile state),根据某个指定的程序库,却无法判断出其中各个类的载入顺序。因此,在load方法中使用其他类是不安全的。比如说:

1
2
3
4
5
6
7
@interface EOCClassB
+ (void)load {
NSLog(@"Loading EOCClassB");
// 此处代码不安全,也许 EOCClassA 方法并没有加载
EOCClassA *obj = [EOCClassA new];
}
@end

该类不能在里面等待锁,也不要调用可能会加锁的方法。凡是在类加载之前执行某些任务的,基本都不太对。其真正的用途是仅仅在调试程序,比如可以在分类里编写此方法,用来判断该分类是否已经正确载入系统中。

想要执行与类初始化有关的操作,还要覆写+ (void)initialize。它与+ (void)load方法都是由 runtime 调用的,并且只调用一次。但是有一些区别:

  • initialize惰性调用的,也就是程序用到相关的类时,才会调用。也就是说应用程序无需将每个类的initialize都执行一遍,这与load方法不同。对于load,应用程序必须阻塞并等着所有类的load都执行完,才能继续。
  • runtime 系统在执行该方法时,是处于正常状态的。因此,此时可以安全使用并调用任意类中的任意方法。而且 runtime 系统能确保initialize方法在“线程安全的环境”中执行。也就是说只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等着initialize执行完。
  • initialize与其他消息一样,如果某个类未实现它,其父类实习那了,那么就会运行父类的实现代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface EOCBaseClass: NSObject
@end

@implementation EOCBaseClass
+ (void)initialize {
NSLog(@"%@ initialize", self);
}
@end


@interface EOCSubClass: NSObject
@end

@implementation EOCSubClass
@end

首次使用 EOCSubClass 时,会输出:

1
2
EOCBaseClass initialize
EOCSubClass initialize

与其他方法一样(load 除外), initialize 也遵循常用的继承规则。所以在初始化 EOCSubClass 时,由于子类未覆写initialize方法,因此还要把父类的实现代码再运行一遍。鉴于此通常会这样实现 initialize 方法:

1
2
3
4
5
6
7
+ (void)initialize {
if (self == [EOCBaseClass class]) {
NSLog(@"%@ initialize", self);

// 初始化内部数据,不应该在这里调用其它方法。
}
}

总结:

  • 再加载阶段,如果类实现了 load 方法,那么系统就会调用它。类的 load 方法比分类的要先调用。与其他方法不同,load 方法不参与覆写机制。
  • 首次使用某个类,系统会向其发送 initialize 消息,此方法遵从普通对象的覆写规则,所以要在初始化方法中判断初始化的是哪个类。
  • load、initialize 方法应该实现的更精简一些,避免循环引用。
  • 常量,基本类型可以在编译期定义。无法编译期设定的全局常量(Objective-C 对象),可以放在 initialize 方法里面初始化。

第52条:NSTimer 会保留目标对象

  • NSTimer 对象会保留其目标对象,直到计数器本身失效位置,调用 invalidate 方法可令计时器失效。另外,一次性的计时器在触发完任务之后也会失效。
  • 反复执行任务的计时器,容易产生循环引用,因为 NSTimer 保留其目标实例,目标实例变量又持有 NSTimer,导致循环引用。
  • 扩充 NSTimer 的功能,通过 block 方式来打破循环引用。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// NSTimer+EOCBlockSupport.h
@interface NSTimer (EOCBlockSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void (^)())block
repeats:(BOOL)repeats;
@end

// NSTimer+EOCBlockSupport.m
@implementation NSTimer (EOCBlockSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void (^)())block
repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(eoc_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}

+ (void)eoc_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userInfo;
if (block) {
block();
}
}

@end


// EOCClass.h
@interface NSTimer : NSObject
@end

// EOCClass.m
@implementation EOCClass {
NSTimer *_pollTimer;
}

- (void)startPolling {
// 通过 wealSelf 来避免实例变量 _pollTimer 持有 NSTimer,NSTimer 又持有 block,block 又持有 self 的三重循环引用。
__weak EOCClass *wealSelf = self;
_pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0
block:^{
EOCClass *strongSelf = wealSelf;
[strongSelf p_doPoll];
} repeats:YES];
}

- (void)p_doPoll {}

@end